//
// Created by LONG on 2020/10/18.
//

#include <string.h>
#include "../../include/utils/hashTable.h"

struct HashNode
{
    S_HashNodeKeyVal keyValue;
    struct HashNode *next;
};

struct HashTable
{
    uint32_t           size;
    uint32_t           elmntNum;
    struct HashNode  **hashNodes;
    struct HashNode   *nodeEntities;
    struct HashNode   *freeNodes;
    HashTableHashFunc  hashFunc;
    HashTableEqualFunc equalFunc;
};

S_HashTable* HashTable_Create(uint32_t size, HashTableHashFunc hashFunc, HashTableEqualFunc equalFunc)
{
    uint32_t loop;
    S_HashTable *hash_tbl;

    if (size < 1)
    {
        return NULL;
    }

    hash_tbl = OS_MemCalloc(1, sizeof(S_HashTable));
    if (NULL == hash_tbl)
    {
        return NULL;
    }

    hash_tbl->hashNodes = OS_MemCalloc(1, size * sizeof(S_HashNode*));
    if (NULL == hash_tbl->hashNodes)
    {
        OS_MemFree(hash_tbl);
        return NULL;
    }

    hash_tbl->nodeEntities = OS_MemCalloc(1, size * sizeof(S_HashNode));
    if (NULL == hash_tbl->nodeEntities)
    {
        OS_MemFree(hash_tbl->hashNodes);
        OS_MemFree(hash_tbl);
        return NULL;
    }

    for (loop = 0; loop < size - 1; loop++)
    {
        hash_tbl->nodeEntities[loop].next = &hash_tbl->nodeEntities[loop + 1];
    }
    hash_tbl->nodeEntities[size - 1].next = NULL;
    hash_tbl->freeNodes = hash_tbl->nodeEntities;
    hash_tbl->size = size;
    hash_tbl->hashFunc = hashFunc;
    hash_tbl->equalFunc = equalFunc;

    return hash_tbl;
}

void HashTable_Destroy(S_HashTable *self)
{
    OS_MemFree(self->hashNodes);
    OS_MemFree(self->nodeEntities);
    OS_MemFree(self);
}

static inline S_HashNode* HashTable_AllocNode(S_HashTable *self)
{
    S_HashNode *node;

    if (NULL == self->freeNodes)
    {
        return NULL;
    }
    node = self->freeNodes;
    self->freeNodes = node->next;

    return node;
}

static inline void HashTable_RecycleNode(S_HashTable *self, S_HashNode *node)
{
    if (self->freeNodes)
    {
        node->next = self->freeNodes;
    }
    else
    {
        node->next = NULL;
    }
    self->freeNodes = node;
}

int32_t HashTable_Insert(S_HashTable *self, void *key, void *data)
{
    uint32_t  hash;
    S_HashNode *node;

    hash = self->hashFunc(key) % self->size;

    node = self->hashNodes[hash];
    while (node)
    {
        if (self->equalFunc(node->keyValue.key, key))
        {
            node->keyValue.data = data;
            return CSP_RC_OK;
        }
        node = node->next;
    }

    node = HashTable_AllocNode(self);
    if (NULL == node)
    {
        return CSP_RC_ER;
    }
    node->keyValue.key = key;
    node->keyValue.data = data;
    if (self->hashNodes[hash])
    {
        node->next = self->hashNodes[hash];
    }
    else
    {
        node->next = NULL;
    }
    self->hashNodes[hash] = node;
    self->elmntNum += 1;

    return CSP_RC_OK;
}

S_HashNodeKeyVal* HashTable_Remove(S_HashTable *self, void *key)
{
    uint32_t  hash;
    S_HashNode *node;
    S_HashNode *prevNode;

    prevNode = NULL;
    hash = self->hashFunc(key) % self->size;
    node = self->hashNodes[hash];
    while (node)
    {
        if (self->equalFunc(node->keyValue.key, key))
        {
            if (prevNode)
            {
                prevNode->next = node->next;
            }
            else
            {
                self->hashNodes[hash] = node->next;
            }
            HashTable_RecycleNode(self, node);
            self->elmntNum -= 1;

            return &node->keyValue;
        }
        prevNode = node;
        node = node->next;
    }

    return NULL;
}

S_HashNodeKeyVal* HashTable_Lookup(S_HashTable *self, void *key)
{
    uint32_t  hash;
    S_HashNode *node;

    hash = self->hashFunc(key) % self->size;
    node = self->hashNodes[hash];
    while (node)
    {
        if (self->equalFunc(node->keyValue.key, key))
        {
            return &node->keyValue;
        }
        node = node->next;
    }

    return NULL;
}

int32_t HashTable_Resize(S_HashTable *self, uint32_t size)
{
    int32_t rc;
    S_HashTable *hashTblNew;
    S_HashNode **hashNodesOld;
    S_HashNode  *nodeEntitiesOld;
    S_HashNodeKeyVal *keyValue;
    S_HashTableIter iter;

    if (size < HashTable_Count(self))
    {
        return CSP_RC_ER;
    }

    hashTblNew = HashTable_Create(size, self->hashFunc, self->equalFunc);
    if (NULL == hashTblNew)
    {
        return CSP_RC_ER;
    }

    hashNodesOld = self->hashNodes;
    nodeEntitiesOld = self->nodeEntities;

    HashTable_Iterate(self, &iter);
    while (HashTableIter_HasMore(&iter))
    {
        keyValue = HashTableIter_Next(&iter);
        rc = HashTable_Insert(hashTblNew, keyValue->key, keyValue->data);
        if (CSP_RC_OK != rc)
        {
            HashTable_Destroy(hashTblNew);
            return CSP_RC_ER;
        }
    }
    memcpy(self, hashTblNew, sizeof(S_HashTable));

    OS_MemFree(hashNodesOld);
    OS_MemFree(nodeEntitiesOld);
    OS_MemFree(hashTblNew);

    return CSP_RC_OK;
}

uint32_t HashTable_Capacity(S_HashTable *self)
{
    return self->size;
}

uint32_t HashTable_Count(S_HashTable *self)
{
    return self->elmntNum;
}

static inline void HashTable_UpdateIterate(S_HashTable *self, S_HashTableIter *iterator, uint32_t index)
{
    uint32_t loop;

    iterator->hashTable = self;
    iterator->nextEntry = NULL;

    for (loop = index; loop < self->size; loop++)
    {
        if (NULL != self->hashNodes[loop])
        {
            iterator->nextEntry = self->hashNodes[loop];
            iterator->nextIndex = loop;
            break;
        }
    }
}

void HashTable_Iterate(S_HashTable *self, S_HashTableIter *iterator)
{
    HashTable_UpdateIterate(self, iterator, 0);
}

int32_t HashTableIter_HasMore(S_HashTableIter *iterator)
{
    return (NULL != iterator->nextEntry);
}

S_HashNodeKeyVal* HashTableIter_Next(S_HashTableIter *iterator)
{
    S_HashNode *currEntry;

    if(NULL == iterator->nextEntry)
    {
        return NULL;
    }

    currEntry = iterator->nextEntry;
    if(NULL != currEntry->next)
    {
        iterator->nextEntry = currEntry->next;
    }
    else
    {
        HashTable_UpdateIterate(iterator->hashTable, iterator, iterator->nextIndex + 1);
    }

    return &currEntry->keyValue;
}
